#pragma once

#include <cstdint>
#include <ctime>
#include <limits>
#include <memory>
#include <stdexcept>
#include <cstdlib>

#if !defined(__linux__)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // avoid including unnecessary WIN32 header files
#endif
#include <windows.h>
#include <winsock2.h>
#define ModuleHandle HMODULE // DLL handle
#define INVALID_MODULE_HANDLE 0
#define MODULE_SUFFIX ".dll"
#undef max
#else
#include <arpa/inet.h>
#include <dirent.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define ModuleHandle void * // LINUX SO handle
#define INVALID_MODULE_HANDLE nullptr
#define MODULE_SUFFIX ".so"
#undef max
#endif // !defined(__linux__)

namespace rpc {

class Transport;
class Proxy;
class Stub;
class ProxyCall;
class StubCall;
class RpcService;
class StubCreator;
class ProxyCreator;
class Logger;

// RPC error
enum class RpcError : std::uint32_t {
  OK = 1,
  NOT_FOUND,
  EXCEPTION,
  TIMEOUT,
};

// RPC message type
enum class RpcMsgType : std::uint32_t {
  NONE = 0,
  RPC_CALL = 1,
  RPC_RETURN,
  RPC_PROXY_CALL,
  RPC_PROXY_RETURN,
  NO_RPC,
};

// RPC service stub type
enum class StubType {
  SINGLE = 1,
  MULTIPLE,
  REENTRANT,
  GENERIC,
};

enum class StubStatus {
  RESOLVED = 1,
  UPDATING,
};

// RPC timeout
constexpr std::time_t RPC_TIMEOUT = 5000;
// Stream buffer size for single RPC message
constexpr int STREAMBUF_SIZE = 1024 * 512;

using ServiceUUID = std::uint64_t;
using ServiceID = std::uint32_t;
using ProxyID = std::uint32_t;
using MethodID = std::uint32_t;
using CallID = std::uint32_t;
using GlobalIndex = std::uint32_t;
using ErrorID = std::uint32_t;
using MsgTypeID = std::uint32_t;
using StubCallID = std::uint32_t;
using TransportPtr = std::shared_ptr<Transport>;
using ProxyPtr = std::shared_ptr<Proxy>;
using ProxyWeakPtr = std::weak_ptr<Proxy>;
using StubPtr = std::shared_ptr<Stub>;
using ProxyCallPtr = std::shared_ptr<ProxyCall>;
using StubCallPtr = std::shared_ptr<StubCall>;
constexpr CallID INVALID_CALL_ID = 0;
constexpr ServiceID INVALID_SERVICE_ID = 0;
constexpr ProxyID INVALID_PROXY_ID = 0;
constexpr MethodID INVALID_METHOD_ID = 0;
constexpr ServiceUUID INVALID_SERVICE_UUID = 0;
constexpr StubCallID INVALID_STUB_CALL_ID = 0;
constexpr GlobalIndex INVALID_GLOBAL_INDEX = 0;
constexpr std::size_t MAX_SERVICE_COUNT =
    std::numeric_limits<std::size_t>::max();
using BundleEntryFunc = RpcService *(*)();
constexpr const char *DEFAULT_ENTRY_NAME = "create";
using BundleRegisterFunc = ServiceUUID (*)(rpc::Logger *);
constexpr const char *DEFAULT_REGISTER_NAME = "onRegister";
using BundleVersionFunc = const char* (*)();
constexpr const char* DEFAULT_VERSION_NAME = "onVersion";
using BundleBuildFunc = bool (*)();
constexpr const char* DEFAULT_BUILD_NAME = "onBuildType";

#if !defined(__linux__)
#define FuncExport extern "C" __declspec(dllexport) // WIN32 DLL exporter
#else
#define FuncExport extern "C" // LINUX shared object exporter
#endif                        // !defined(__linux__)

#define co(expr) coro_start([&](void *) { expr });

class MethodNotFound : public std::runtime_error {
public:
  MethodNotFound(const char *reason) : std::runtime_error(reason) {}
};

class RemoteMethodException : public std::runtime_error {
public:
  RemoteMethodException(const char *reason) : std::runtime_error(reason) {}
};

class RemoteMethodTimeout : public std::runtime_error {
public:
  RemoteMethodTimeout(const char *reason) : std::runtime_error(reason) {}
};

class ProxyCoroMethodException : public std::runtime_error {
public:
  ProxyCoroMethodException(const char *reason) : std::runtime_error(reason) {}
};

inline std::uint64_t htonll(std::uint64_t ui64) {
  return (((std::uint64_t)htonl((std::uint32_t)ui64)) << 32) +
         htonl(ui64 >> 32);
}

inline std::uint64_t ntohll(uint64_t ui64) {
  return (((std::uint64_t)ntohl((std::uint32_t)ui64)) << 32) +
         ntohl(ui64 >> 32);
}

#if defined(_WIN32) || defined(_WIN64)
#pragma pack(push)
#pragma pack(1)
#else
#pragma pack(1)
#endif /* defined(_WIN32) || defined(_WIN64) */

struct RpcMsgHeader {
  std::uint32_t length{0};
  MsgTypeID type{0};
  operator bool() const { return length != 0; }
  void hton() {
    length = htonl(length);
    type = htonl(type);
  }
  void ntoh() {
    length = ntohl(length);
    type = ntohl(type);
  }
};

struct RpcCallRequestHeader {
  ServiceUUID serviceUUID{INVALID_SERVICE_UUID};
  ServiceID serviceID{INVALID_SERVICE_ID};
  CallID callID{INVALID_CALL_ID};
  MethodID methodID{INVALID_METHOD_ID};
  void hton() {
    serviceUUID = htonll(serviceUUID);
    serviceID = htonl(serviceID);
    callID = htonl(callID);
    methodID = htonl(methodID);
  }
  void ntoh() {
    serviceUUID = ntohll(serviceUUID);
    serviceID = ntohl(serviceID);
    callID = ntohl(callID);
    methodID = ntohl(methodID);
  }
};

struct RpcProxyCallRequestHeader {
  ServiceUUID serviceUUID{INVALID_SERVICE_UUID};
  ServiceID serviceID{INVALID_SERVICE_ID};
  CallID callID{INVALID_CALL_ID};
  MethodID methodID{INVALID_METHOD_ID};
  GlobalIndex globalIndex{INVALID_GLOBAL_INDEX};
  std::uint16_t oneWay{0};

  void hton() {
    serviceUUID = htonll(serviceUUID);
    serviceID = htonl(serviceID);
    callID = htonl(callID);
    methodID = htonl(methodID);
    globalIndex = htonl(globalIndex);
    oneWay = htons(oneWay);
  }
  void ntoh() {
    serviceUUID = ntohll(serviceUUID);
    serviceID = ntohl(serviceID);
    callID = ntohl(callID);
    methodID = ntohl(methodID);
    globalIndex = ntohl(globalIndex);
    oneWay = ntohs(oneWay);
  }
};

struct RpcCallRetHeader {
  ServiceID serviceID{INVALID_SERVICE_ID};
  CallID callID{INVALID_CALL_ID};
  ErrorID errorID{0};
  void hton() {
    serviceID = htonl(serviceID);
    callID = htonl(callID);
    errorID = htonl(errorID);
  }
  void ntoh() {
    serviceID = ntohl(serviceID);
    callID = ntohl(callID);
    errorID = ntohl(errorID);
  }
};

struct RpcProxyCallRetHeader {
  ServiceID serviceID{INVALID_SERVICE_ID};
  CallID callID{INVALID_CALL_ID};
  ErrorID errorID{0};
  GlobalIndex globalIndex{INVALID_GLOBAL_INDEX};
  void hton() {
    serviceID = htonl(serviceID);
    callID = htonl(callID);
    errorID = htonl(errorID);
    globalIndex = htonl(globalIndex);
  }
  void ntoh() {
    serviceID = ntohl(serviceID);
    callID = ntohl(callID);
    errorID = ntohl(errorID);
    globalIndex = ntohl(globalIndex);
  }
};

struct RpcCallHeader {
  RpcMsgHeader header;
  RpcCallRequestHeader callHeader;
  void hton() {
    header.hton();
    callHeader.hton();
  }
  void ntoh() {
    header.ntoh();
    callHeader.ntoh();
  }
};

struct RpcProxyCallHeader {
  RpcMsgHeader header;
  RpcProxyCallRequestHeader callHeader;
  void hton() {
    header.hton();
    callHeader.hton();
  }
  void ntoh() {
    header.ntoh();
    callHeader.ntoh();
  }
};

struct RpcRetHeader {
  RpcMsgHeader header;
  RpcCallRetHeader retHeader;
  void hton() {
    header.hton();
    retHeader.hton();
  }
  void ntoh() {
    header.ntoh();
    retHeader.ntoh();
  }
};

struct RpcProxyRetHeader {
  RpcMsgHeader header;
  RpcProxyCallRetHeader retHeader;
  void hton() {
    header.hton();
    retHeader.hton();
  }
  void ntoh() {
    header.ntoh();
    retHeader.ntoh();
  }
};

#if defined(_WIN32) || defined(_WIN64)
#pragma pack(pop)
#else
#pragma pack()
#endif /* defined(_WIN32) || defined(_WIN64) */

/** RPC proxy handler */
class ProxyHandler {
public:
  virtual ~ProxyHandler() {}
  /**
   * Invoked by framework when RPC message arriving
   *
   * 1. Proxy implementation should maintain relationship between
   * call ID and {service UUID, client transport, internal service's transport}
   * 2. Proxy implementation should generate client ID and maintain relationship
   * between client ID and client transport
   *
   * @param transport transportation
   * @param header RPC protocol header
   */
  virtual bool onRelay(TransportPtr& transport, const RpcMsgHeader &header) = 0;
};

/** Returns RPC error string */
extern const char *getErrorString(RpcError error);

} // namespace rpc
